/**
* @author SowingSadness
* @mail sowingsadness@gmail.com
*/

(function ($, undefined)
{
	function fixDefineGetterSetter()
	{
		if (!HTMLElement) {
			var HTMLElement = HTMLDivElement;
		}
		if (!HTMLElement.prototype.__defineGetter__) {
			HTMLElement.prototype.__defineGetter__ = function (property, func)
			{
				Object.defineProperty(this, property, { get: func });
			};
		};
	}
	fixDefineGetterSetter();

	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function (elt /*, from*/)
		{
			var len = this.length >>> 0;

			var from = Number(arguments[1]) || 0;
			from = (from < 0) ? Math.ceil(from) : Math.floor(from);
			if (from < 0)
				from += len;

			for (; from < len; from++) {
				if (from in this && this[from] === elt)
					return from;
			}
			return -1;
		};
	}

	var TEMPLATE_NAME_SKELETON = 'skeletonTmpl';
	var TEMPLATE_NAME_LIST = 'listTmpl';
	var TEMPLATE_NAME_ITEM = 'optionTmpl';
	var TEMPLATE_SKELETON = '<div data-jqc="holder" id="${id}" name="${name}" class="${classes}" tabindex="${tabindex}" style="width:${width};height:${height};" role="combobox"> <div data-jqc="control"> <div data-jqc="open-button"></div> <div data-jqc="visible-value item"></div> <input data-jqc="input" name="${titlename}" type="text" tabindex="-1" /> <input data-jqc="value" name="${name}" type="hidden"/> </div> <div data-jqc="list-wraper" style="width:${width};"> </div></div>';

	var TEMPLATE_LIST = '<ul></ul>';
	var TEMPLATE_ITEM = '<li data-value="${value}" data-group="${group}" data-filter="${name}">${name}</li>';

	var templater = {
		template: function (sTemplateName, sValue)
		{
			$.template(sTemplateName, sValue);
		},
		tmpl: function (sTemplateName, aData)
		{
			return $.tmpl(sTemplateName, aData);
		}
	};

	var controlConstructor = {
		getDataAttrs: function ($Object)
		{
			var prefix = 'data-';
			var oAttrsData = {};
			var sAttrs = $Object[0].attributes;
			if (sAttrs !== undefined) {
				for (var i = 0; i < sAttrs.length; i++) {
					if (sAttrs[i].name.substr(0, prefix.length).toLowerCase() === prefix) {
						var sAttrName = sAttrs[i].name.substr(prefix.length);
						oAttrsData[sAttrName] = sAttrs[i].textContent ? sAttrs[i].textContent : sAttrs[i].value;
					}
				}
			}

			return oAttrsData;
		},
		generateSourceListByDOM: function ($source)
		{
			var aItems = [];
			$source.children('option').each(function (nIndex, domElement)
			{
				$oItem = $(domElement);
				var oData = {
					name: $oItem.text(),
					value: $oItem.val()
				};
				var aExtendData = controlConstructor.getDataAttrs($oItem);
				$.extend(oData, aExtendData);
				aItems.push(oData);
			});

			return aItems;
		},
		addARIAItems: function (pluginObject)
		{
			pluginObject.DOMElements.items.attr('role', 'option');
		},
		addARIASkeleton: function (pluginObject)
		{
			//TODO: add: aria-owns, aria-activedescendant, ria-expanded
			pluginObject.DOMElements.holder.attr('role', 'combobox').attr('aria-autocomplete', 'true');
			pluginObject.DOMElements.listWraper.attr('role', 'listbox');
			pluginObject.DOMElements.listWraper.attr('aria-live', 'polite');
		},
		prepareOptions: function (pluginObject)
		{
			var oOptions = pluginObject.options;
			/*
			* generate for uniq event id
			*/
			oOptions.__id = Math.floor(Math.random() * Date.parse(new Date()) / 10000).toString();
			if (!oOptions.source) {
				oOptions.source = pluginObject.element;
			}
			if (!oOptions.target) {
				oOptions.target = pluginObject.element;
			}
			if (!(oOptions.source instanceof Function)) {
				oOptions.autocomplete = false;
			}
			if (oOptions.autocomplete || oOptions.filter) {
				oOptions.input = true;
			}
			if (oOptions.autocomplete) {
				oOptions.filter = false;
			}
			if (oOptions.tabindex === undefined || oOptions.tabindex === true) {
				oOptions.tabindex = 0;
			} else if (oOptions.tabindex === false) {
				oOptions.tabindex = -1;
			}
			if (oOptions.size === undefined) {
				oOptions.size = oOptions.source[0] && oOptions.source[0].size ? oOptions.source[0].size : 0;
			}
		},
		prepareSource: function (pluginObject)
		{
			var source = pluginObject.options.source;
			var target = pluginObject.options.target;
			var data;
			if (source === undefined) {
				return null;
			}
			if (target !== undefined) {
				data = {
					'id': target.attr('id'),
					'name': target.attr('name'),
					'titlename': target.attr('data-titlename'),
					'tabindex': target.attr('tabindex'),
					'classes': target.attr('class')
				};
			}
			if (source instanceof $) {
				data['id'] = source.attr('id');
				data['name'] = source.attr('name');
				data['titlename'] = source.attr('data-titlename');
				data['tabindex'] = source.attr('tabindex');
				data['classes'] = source.attr('class');
				data['list'] = this.generateSourceListByDOM(source);

				var selectedItem = source.children('option[selected]');
				var selectedValue = selectedItem.val();
				var selectedDataVal = selectedItem.attr('data-value');
				if (selectedDataVal === undefined) {
					selectedDataVal = selectedValue;
				}

				data['selected'] = selectedDataVal;
			} else if (source instanceof Array) {
				data['list'] = source;
			} else if (source instanceof Function) {
				data['list'] = [];
			} else {
				return null;
			}

			pluginObject.options = $.extend(data, pluginObject.options);
		},
		generateDOMElements: function (pluginObject)
		{
			var objects = {};
			templater.template(TEMPLATE_NAME_SKELETON, TEMPLATE_SKELETON);
			templater.template(TEMPLATE_NAME_ITEM, pluginObject.options[TEMPLATE_NAME_ITEM]);

			var holder = templater.tmpl(TEMPLATE_NAME_SKELETON, pluginObject.options);
			var listWraper = holder.find('[data-jqc="list-wraper"]');
			var list = $(pluginObject.options[TEMPLATE_NAME_LIST]);

			listWraper.append(list);

			objects.holder = holder;
			objects.listWraper = listWraper;
			objects.list = list.attr('data-jqc', 'list');
			objects.input = holder.find('[data-jqc="input"]');
			objects.value = holder.find('input[data-jqc="value"]');
			objects.visible = holder.find('[data-jqc~="visible-value"]');
			// Support jQuery::jqmbx on DOM element
			objects.holder.data('jqcmbx', pluginObject);

			return objects;
		},
		prepareDOM: function (pluginObject)
		{
			/// <summary>
			///	prepare DOM element by option data
			/// </summary>
			/// <param name="pluginObject" optional="false" type="TpluginObject" parameterArray="false" integer="false">Plugin object</param>

			if (pluginObject.options.input) {
				pluginObject.DOMElements.input.css('z-index', 3);
			} else {
				pluginObject.DOMElements.input.css('z-index', 2);
			}
		},
		attachEventsHooks: function (pluginObject)
		{
			pluginObject.DOMElements.holder.bind('focus.jqcmbx', function (event)
			{
				pluginObject._focus(event);
			});
			pluginObject.DOMElements.holder.bind('focusin.jqcmbx', function (event)
			{
				pluginObject._setNoCloseStatus(false);
				clearTimeout(pluginObject.status.closeTimer);
			});
			pluginObject.DOMElements.holder.bind('focusout.jqcmbx', function (event)
			{
				pluginObject.status.closeTimer = setTimeout(function ()
				{
					pluginObject._blur(event);
				}, 150);
			});
			pluginObject.DOMElements.holder.bind('click.jqcmbx', function (event)
			{
				pluginObject._click(event);
			});
			pluginObject.DOMElements.holder.delegate('[data-jqc="item"]', 'click.jqcmbx', function (event)
			{
				pluginObject._selectItem(event, this);
				event.stopPropagation();
			});
			pluginObject.DOMElements.input.bind('focusin.jqcmbx', function (event)
			{
				pluginObject._setNoCloseStatus(false);
				pluginObject.open(event);
			});

			pluginObject.DOMElements.holder.bind('keydown.jqcmbx', function (event)
			{
				pluginObject._trigger('keydown', event);

				pluginObject.keysHooks(event);
			});
			pluginObject.DOMElements.holder.bind('keypress.jqcmbx', function (event)
			{
				pluginObject._trigger('keypress', event); 
			});
			pluginObject.DOMElements.holder.bind('keyup.jqcmbx', function (event)
			{
				pluginObject._trigger('keyup', event);
				if (!pluginObject.isOpen()) { return false; }
				if (pluginObject.options.filter) { pluginObject.filterList(event); }
				if (pluginObject.options.autocomplete) { pluginObject.autocomplete(event); }
			});
		},
		attachSelectMethodsToDOM: function (pluginObject, DOMElement)
		{
			if (DOMElement.__defineGetter__) {
				DOMElement.__defineGetter__("selectedIndex", function () { return pluginObject.selectedIndex(); });
				DOMElement.__defineGetter__("length", function () { return pluginObject.length(); });
				DOMElement.__defineGetter__("size", function () { return pluginObject.size(); });
				DOMElement.__defineGetter__("type", function () { return pluginObject.type(); });
				DOMElement.__defineGetter__("name", function () { return pluginObject.name(); });
				/**
				* NOT standart properties
				*/
				DOMElement.__defineGetter__("value", function () { return pluginObject.value(); });
			}
			DOMElement.add = pluginObject.add;
			DOMElement.remove = pluginObject.remove;
			DOMElement.removeAll = pluginObject.removeAll;
		},
		attachOptionsPropertyDOM: function (pluginObject, DOMElement)
		{
			var OptionsPrototype = new Array();
			OptionsPrototype.item = function (index) { return this[index]; };
			OptionsPrototype.namedItem = function (name) { /*none*/ };
			OptionsPrototype.add = function (option, before) { pluginObject.add(option); };
			OptionsPrototype.remove = function (index) { pluginObject.remove(index); };
			if (OptionsPrototype.__defineGetter__) {
				OptionsPrototype.__defineGetter__("selectedIndex", function () { return pluginObject.selectedIndex(); });
			}

			var Options = pluginObject.DOMElements.items;
			Options.constructor.prototype = OptionsPrototype;

			DOMElement.options = Options;
		},
		resizeItemsList: function (pluginObject)
		{
			var show = pluginObject.isOpen();

			//If item not visible, we cant calcualate size
			pluginObject.DOMElements.listWraper.css('visibility', 'hidden');
			pluginObject.DOMElements.listWraper.show();
			var iItemHeight = this.calculateItemHeight(pluginObject);
			var iCurrentMaxHeight = iItemHeight * pluginObject.DOMElements.list.children(':visible').length;
			if (!show) {
				pluginObject.DOMElements.listWraper.hide();
			}
			pluginObject.DOMElements.listWraper.css('visibility', 'visible');
			if (pluginObject.options.size) {
				var iItemListHeight = pluginObject.DOMElements.items.length ? (pluginObject.options.size * iItemHeight) : iCurrentMaxHeight;
				if (iItemListHeight > iCurrentMaxHeight) {
					iItemListHeight = iCurrentMaxHeight;
				}
			} else {
				iItemListHeight = iCurrentMaxHeight;
			}
			iItemListHeight = iItemListHeight + this.calculateListBorderWidth(pluginObject) * 2;
			var sItemListHeight = iItemListHeight + 'px';
			pluginObject.DOMElements.list.css('height', sItemListHeight);
		},
		reposScrollArea: function (pluginObject)
		{
			if (pluginObject.options.size) {
				var $activeItem = pluginObject.DOMElements.list.children('.active');
				if ($activeItem.length === 0) {
					$activeItem = pluginObject.DOMElements.list.children(':visible:first');
					if ($activeItem.length === 0) {
						return;
					}
				}

				var iItemHeight = this.calculateItemHeight(pluginObject);
				var iViewYRange = pluginObject.options.size * iItemHeight;
				var iTopRange = pluginObject.DOMElements.list.scrollTop();
				var iDownRange = iViewYRange;
				var iOffsetTop = $activeItem.position().top - this.calculateListBorderWidth(pluginObject);
				if (iOffsetTop < 0) {
					pluginObject.DOMElements.list.scrollTop(iTopRange + iOffsetTop);
				} else if (iOffsetTop >= iDownRange) {
					pluginObject.DOMElements.list.scrollTop(iTopRange + iOffsetTop - iDownRange + iItemHeight);
				}
			}
		},
		calculateItemHeight: function (pluginObject)
		{
			return $(pluginObject.DOMElements.items[0]).outerHeight(true); //может брать среднее арифметическое?
		},
		calculateListBorderWidth: function (pluginObject)
		{
			var value = pluginObject.DOMElements.list.css('border-width');
			if (!value) {
				value = 0;
			} else {
				value = parseInt(value);
			}
			return value;
		},
		returnHookKeys: function ()
		{
			return [$.ui.keyCode.RIGHT, $.ui.keyCode.DOWN, $.ui.keyCode.LEFT, $.ui.keyCode.UP, $.ui.keyCode.ENTER];
		}
	};

	var TpluginObject = {
		DOMElements: {
			'holder': undefined,
			'control': undefined,
			'input': undefined,
			'visible': undefined,
			'value': undefined,
			'listWraper': undefined,
			'list': undefined,
			'items': undefined,
			'activeItem': undefined,
			'tabHolder': undefined
		},
		status: {
			'open': false,
			'noClose': false,
			'noOpen': false,
			'closeTimer': undefined
		},
		/**
		* jQuery.ui.widget::options
		*/
		options: {
			'target': undefined,
			'source': undefined,
			'listTmpl': TEMPLATE_LIST,
			'optionTmpl': TEMPLATE_ITEM,
			'width': '128px',
			'height': '22px',
			'filter': true,
			'size': undefined,
			'tabindex': undefined,
			'input': false,
			'autocomplete': false,
			'delay': 300,
			'delay-instance': undefined,
			'minLength': 1,
			'aria': true,
			/**
			* source-info
			*/
			'id': undefined,
			'name': undefined,
			'titlename': undefined,
			'selected': undefined,
			'classes': undefined,
			'list': undefined
		},
		_isFixEvent: function (event)
		{
			if (!event) {
				return false;
			}
			switch (event.type) {
				case ('focus'):
				case ('focusin'):
					if (this.isOpen()) {
						return true;
					}
					break;
				case ('blur'):
				case ('focusout'):
					if (!this.isOpen()) {
						return true;
					}
					break;
			}
			return false;
		},
		_openList: function (event)
		{
			this.DOMElements.listWraper.show();
			this._setOpenStatus(true);
			controlConstructor.reposScrollArea(this);
		},
		_closeList: function (event)
		{
			this.DOMElements.listWraper.hide();
			this._setOpenStatus(false);
		},
		_hideInput: function (event)
		{
			this.DOMElements.input.css('visibility', 'hidden');
			this.DOMElements.visible.css('visibility', 'visible');
		},
		_showInput: function (event)
		{
			if (this.options.input) {
				this.DOMElements.visible.css('visibility', 'hidden');
				this.DOMElements.input.css('visibility', 'visible');
				this._setNoCloseStatus(true);
				this.DOMElements.input[0].focus();
			}
		},
		open: function (event)
		{
			if (this._isFixEvent(event)) { return true; }
			if (this.isNoOpen()) { return true; }

			this._openList(event);

			this._showInput(event);
		},
		close: function (event)
		{
			if (this._isFixEvent(event)) { return true; }
			if (this.isNoClose()) { return true; }
			this._closeList(event);
			this._hideInput(event);
			this._setNoOpenStatus(true);
			this.DOMElements.holder[0].focus();
			this._setNoOpenStatus(false);
		},
		_focus: function (event)
		{
			this._trigger('focus');

			if (this.options.autocomplete) {
				this.open();
			}
			this._setNoOpenStatus(false);
		},
		_blur: function (event)
		{
			this.close(event);
			this._trigger('blur');
		},
		_click: function (event)
		{
			if (!this.isOpen()) {
				this.open(event);
			} else {
				this.close(event);
			}
		},
		_selectItem: function (event, $activeItem)
		{
			if ($activeItem === undefined) {
				$activeItem = event.currentTarget;
			}
			$activeItem = $($activeItem);
			this.setActiveItem($activeItem);
			this.close(event);
		},
		_setOption: function (key, value)
		{
			$.Widget.prototype._setOption.apply(this, arguments);
			if (key === "input") {
				if (value) {
					this.DOMElements.input.css('z-index', 3).css('visibility', 'visible');
				} else {
					this.DOMElements.input.css('z-index', 2).css('visibility', 'hidden');
				}
			}
		},
		isOpen: function ()
		{
			return this.status.open;
		},
		_setOpenStatus: function (isOpen)
		{
			this.status.open = isOpen;
		},
		isNoOpen: function ()
		{
			return this.status.noOpen;
		},
		_setNoOpenStatus: function (noOpen)
		{
			this.status.noOpen = noOpen;
		},
		isNoClose: function ()
		{
			return this.status.noClose;
		},
		_setNoCloseStatus: function (noClose)
		{
			this.status.noClose = noClose;
		},
		keysHooks: function (event)
		{
			switch (event.which) {
				case $.ui.keyCode.RIGHT:
					break;
				case $.ui.keyCode.DOWN:
					//вниз
					event.preventDefault();
					this.shiftActiveItem('next');
					return false;
					break;
				case $.ui.keyCode.LEFT:
					break;
				case $.ui.keyCode.UP:
					//вверх
					event.preventDefault();
					this.shiftActiveItem('prev');
					return false;
					break;
				case $.ui.keyCode.ESCAPE:
				case $.ui.keyCode.ENTER:
					event.preventDefault();
					this._setNoCloseStatus(false);
					this.close(event);
					break;
				case $.ui.keyCode.TAB:
					this._setNoCloseStatus(false);
					this.close(event);
					break;
			}
		},
		shiftActiveItem: function (direction)
		{
			var selectShift, slectorShift;
			var visibleModificator = this.isOpen() ? ':visible' : '';
			if (direction === 'next') {
				selectShift = function ($activeItem) { return $activeItem.nextAll(visibleModificator + ':first'); };
				slectorShift = visibleModificator + ':first';
			} else {
				selectShift = function ($activeItem) { return $activeItem.prevAll(visibleModificator + ':first'); };
				slectorShift = visibleModificator + ':last';
			}

			var $activeItem = this.DOMElements.list.children('.active');
			if ($activeItem.length === 0) {
				$activeItem = this.DOMElements.list.children(slectorShift);
			} else {
				var $shiftItem = selectShift($activeItem);
				if ($shiftItem.length === 0) {
					$activeItem = this.DOMElements.list.children(slectorShift);
				} else {
					$activeItem = $shiftItem;
				}
			}

			this.setActiveItem($activeItem);
			controlConstructor.reposScrollArea(this);
		},
		setActiveItem: function ($activeItem)
		{
			this.DOMElements.items.removeClass('active');
			$activeItem.addClass('active');
			this.DOMElements.activeItem = $activeItem;

			var filter = $activeItem.data('filter');
			if (filter === undefined) {
				filter = $activeItem.text();
			}

			this.DOMElements.input.val(filter);
			this.value($activeItem.data('value'));
			this.DOMElements.visible.html($activeItem.html());
		},
		getActiveItem: function (event)
		{
			return this.DOMElements.activeItem;
		},
		filterList: function (event)
		{
			var arrKeyCodes = controlConstructor.returnHookKeys();
			if (arrKeyCodes.indexOf(event.keyCode) > 0) {
				return true;
			}

			var enteredValue = this.DOMElements.input.val().toLowerCase().replace(/\s+/, "");
			for (var i = 0; i < this.DOMElements.items.length; i++) {
				var $item = this.DOMElements.items.filter(function (index) { return index === i; });
				var filter = $item.attr('data-filter').toLowerCase();
				if ((filter !== undefined) && (filter.match(enteredValue) !== null)) {
					$item.show();
				} else {
					$item.hide();
				}
			}
			controlConstructor.resizeItemsList(this, true);
		},
		autocomplete: function (event)
		{
			var arrKeyCodes = controlConstructor.returnHookKeys();
			if (arrKeyCodes.indexOf(event.keyCode) > 0) {
				return true;
			}

			var pluginObject = this;
			var enteredValue = this.DOMElements.input.val().toLowerCase();

			clearTimeout(this.options['delay-instance']);
			if (enteredValue.length < this.options.minLength) { return; }
			this.options['delay-instance'] = setTimeout(function ()
			{
				pluginObject.options.source.call(pluginObject, enteredValue);
			}, this.options.delay);
		},
		add: function (mData) //before not support
		{
			if (!(mData instanceof Array)) {
				mData = [mData];
			}
			templater.template(TEMPLATE_NAME_ITEM, this.options[TEMPLATE_NAME_ITEM]);
			this.DOMElements.items = templater.tmpl(TEMPLATE_NAME_ITEM, mData);
			this.DOMElements.items.appendTo(this.DOMElements.list);
			this.DOMElements.items.attr("data-jqc", "item");

			controlConstructor.resizeItemsList(this);
		},
		remove: function (index)
		{
			this.DOMElements.remove('eq(' + index + ')');
			controlConstructor.resizeItemsList(this);
		},
		removeAll: function ()
		{
			this.DOMElements.items.remove();
			controlConstructor.resizeItemsList(this);
		},
		update: function (mData)
		{
			this.removeAll();
			this.add(mData);
			controlConstructor.resizeItemsList(this);
		},
		_create: function ()
		{
			this.status = {
				'open': false,
				'noClose': false,
				'noOpen': false,
				'closeTimer': undefined
			};
			controlConstructor.prepareOptions(this);
			controlConstructor.prepareSource(this);
			this.DOMElements = controlConstructor.generateDOMElements(this);
			controlConstructor.prepareDOM(this);
			controlConstructor.attachEventsHooks(this);
			this.options.target.replaceWith(this.DOMElements.holder);

			this.add(this.options.list);

			// ARIA roles
			if (this.options.aria) {
				controlConstructor.addARIASkeleton(this);
				controlConstructor.addARIAItems(this);
			}

			var $activeItem = this.DOMElements.list.find('[data-value="' + this.options.selected + '"]');
			this.setActiveItem($activeItem);
			controlConstructor.attachSelectMethodsToDOM(this, this.DOMElements.holder[0]);
		},
		selectedIndex: function (value)
		{
			if (value !== undefined) {
				var index = parseInt(value);
				if (!this.DOMElements.items[index]) { return false; }
				this._selectItem(this.status.lastEvent, this.DOMElements.items[index]);
				this._trigger('change', this.status.lastEvent);
				return true;
			}

			if (!this.DOMElements.activeItem || this.DOMElements.activeItem.length === 0) {
				return 0;
			}
			for (var x = 0; x < this.DOMElements.items.length; x++) {
				if (this.DOMElements.items[x] === this.DOMElements.activeItem[0]) {
					return x;
				}
			}
			return;
		},
		value: function (value)
		{
			if (value === undefined) {
				return this.DOMElements.value.val();
			} else {
				return this.DOMElements.value.val(value);
			}
		},
		length: function (value)
		{
			return this.DOMElements.items.length;
		},
		size: function (value)
		{
			return this.options.size;
		},
		type: function (value)
		{
			return 'select-one';
		},
		name: function (value)
		{
			return this.DOMElements.value.attr('name');
		}
	};

	$.widget("ui.jqcmbx", TpluginObject);
})(jQuery);